/******************************************************************************
This file is part of AppKit.
Project: appkit
Author : FergusZeng
Email  : cblock@126.com
git	   : https://gitee.com/newgolo/appkit.git
*******************************************************************************
MIT License

Copyright (c) 2022 cblock@126.com

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
******************************************************************************/
#pragma once

#include <map>
#include <memory>
#include <string>

#include "appkit/basetype.h"
#include "appkit/datetime.h"
#include "appkit/fileio.h"
#include "appkit/singleton.h"
#include "appkit/thread.h"

/**
 * @file logger.h
 * @brief 一个简单的日志记录器
 */
namespace appkit {
#define LOG_INFO(logName, fmt, arg...)                                       \
    do {                                                                     \
        LoggerManager::getInstance().getLogger(logName)->log(                \
            "[%s]<I>" fmt, CSTR(appkit::DateTime::getDateTime().toString()), \
            ##arg);                                                          \
    } while (0);
#define LOG_WARN(logName, fmt, arg...)                                       \
    do {                                                                     \
        LoggerManager::getInstance().getLogger(logName)->log(                \
            "[%s]<W>" fmt, CSTR(appkit::DateTime::getDateTime().toString()), \
            ##arg);                                                          \
    } while (0);
#define LOG_ERR(logName, fmt, arg...)                                        \
    do {                                                                     \
        LoggerManager::getInstance().getLogger(logName)->log(                \
            "[%s]<E>" fmt, CSTR(appkit::DateTime::getDateTime().toString()), \
            ##arg);                                                          \
    } while (0);

/**
 * @class Logger
 * @brief 日志器
 */
class Logger {
    DECL_CLASSMETA(Logger)

public:
    Logger();
    ~Logger();
    /**
     * @brief 打开日志
     * @param logFileName 日志文件名
     * @param maxSize 日志文件最大大小
     * @param rotations 循环日志最大文件个数
     * @return true
     * @return false
     */
    bool open(const std::string& logFileName, uint32 maxSize,
              uint32 rotations = 0);
    /**
     * @brief 关闭日志
     */
    void close();
    /**
     * @brief 记录日志
     * @param format 格式化字符串
     * @param ... 可变参数列表
     */
    void log(const char* format, ...);

private:
    uint32 m_writens{0};
    uint32 m_maxSize{0};
    uint32 m_rotations{0};
    uint32 m_logIdx{0};
    std::string m_fileName{""};
    std::shared_ptr<File> m_file{nullptr};
    std::unique_ptr<char[]> m_logBufPtr{nullptr};
    std::mutex m_mutex;
};

/**
 * @class LoggerManager
 * @brief 日志管理器
 */
class LoggerManager : public Singleton<LoggerManager> {
    DECL_CLASSMETA(LoggerManager)
    DECL_SINGLETON(LoggerManager)

public:
    virtual ~LoggerManager();
    /**
     * @brief 设置日志根目录
     * @param logDir
     */
    void setRoot(const std::string& logDir);
    /**
     * @brief 获取日志器
     * @param logName
     * 日志名称(日志管理器会在日志根目录下自动创建logName.log的日志文件)
     * @param maxSize 单个文件最大大小
     * @param rotations
     * 循环记录文件个数(0:不循环记录,>=1:至多创建rotations个记录文件)
     * @return true
     * @return false
     * @note
     * 循环记录文件命名为logNameDD,DD是整数(例:rotations=1时,当logName达到maxSize后,
     *       将创建一个logName01文件用于循环记录)
     */
    bool createLogger(const std::string& logName, int maxSize,
                      int rotations = 0);
    /**
     * @brief 获取日志器
     * @param logName 日志名称
     * @return std::shared_ptr<Logger> 日志器
     * @note 日志管理器根据日志名称来区别日志
     */
    std::shared_ptr<Logger> getLogger(const std::string logName);

private:
    std::map<std::string, std::shared_ptr<Logger>> m_loggerMap;
    std::string m_logDir{""};
};

/**
 * @brief 日志清理器
 *
 */
class LogCleaner {
    DECL_CLASSMETA(LogCleaner)
public:
    class LogSorting {
    public:
        LogSorting(const std::string& fileName) : m_name(fileName) {
            // app_20230101_000000.log
            auto pos = fileName.find(".log");
            if (pos != std::string::npos) {
                auto year = std::stoi(fileName.substr(pos - 15, 4));
                auto month = std::stoi(fileName.substr(pos - 11, 2));
                auto date = std::stoi(fileName.substr(pos - 9, 2));
                auto hour = std::stoi(fileName.substr(pos - 6, 2));
                auto minute = std::stoi(fileName.substr(pos - 4, 2));
                auto second = std::stoi(fileName.substr(pos - 2, 2));
                DateTime dt(year, month, date, hour, minute, second);
                m_ts = dt.toEpochTime().toMicroSec();
            }
        }
        /**
         * @brief 运算符重载,用于排序
         * @param ls 日志排序器
         */
        virtual bool operator<(const LogSorting& ls) const {
            return m_ts < ls.m_ts;
        }
        std::string name() { return m_name; }

    private:
        std::string m_name;
        appkit::uint64 m_ts{0};
    };

public:
    LogCleaner() {}
    /**
     * @brief 清理日志
     * @tparam T LogName类型
     * @param rootDir 日志文件所在根目录
     * @param logPattern 日志匹配规则(正则表达式)
     * @param maxLogs 最大日志文件个数
     * @note 例如:clean("/var/log/applog", "^.*_[0-9]{8}-[0-9]{6}.log")
     *       用于清理APP日志:/var/log/applog/app_20220101-010000.log
     */
    template <typename T>
    bool clean(const std::string& rootDir, const std::string& logPattern,
               int maxLogs) {
        bool ret = true;
        auto fileList = Directory::getFileList(rootDir, logPattern);
        if (fileList.size() <= maxLogs) {
            return ret;
        }
        // 优先队列(大顶堆)
        appkit::PrioQueue<T> logQueue;
        for (auto& file : fileList) {
            T ls(file);
            logQueue.push(ls);
        }
        if (logQueue.size() >= maxLogs) {
            // 保留maxFiles-1个
            int keeps = maxLogs - 1;
            for (auto i = 0; i < keeps; i++) {
                logQueue.pop();
            }
            // 剩余的删除
            int size = logQueue.size();
            for (auto i = 0; i < size; i++) {
                auto logFile = logQueue.pop();
                auto filePath = std::string(rootDir).append("/");
                filePath.append(logFile.name());
                if (!appkit::File::removeFile(filePath)) {
                    ret = false;
                }
            }
        }
        return ret;
    }
};

}  // namespace appkit
